package org.raccoon.sequence.core.factory;

import org.raccoon.sequence.core.entity.Sequence;
import org.raccoon.sequence.core.exception.SequenceException;
import java.util.HashSet;
import java.util.Set;

/**
 * Sequence 简 单 工 厂 -- [就眠仪式]
 * */
public class SequenceFactoryImpl implements SequenceFactory{

    private long workerId;
    private long centerId;
    private long sequence = 0L;
    private long lastTimestamp = -1L;

    private final long workerIdBits = 5L;
    private final long centerIdBits = 5L;
    private final long poc = 1288834974657L;
    private final long sequenceBits = 12L;

    private final long workerIdShift = sequenceBits;
    private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
    private final long maxCenterId = -1L ^ (-1L << centerIdBits);
    private final long centerIdShift = sequenceBits + workerIdBits;
    private final long timestampLeftShift = sequenceBits + workerIdBits + centerIdBits;
    private final long sequenceMask = -1L ^ (-1L << sequenceBits);

    private static SequenceFactoryImpl sequenceFactory;

    /**
     * 懒 汉 单 例 Example chemical plant
     * */
    public static synchronized SequenceFactoryImpl getInstance(long workerId, long centerId){

        if(sequenceFactory == null){
            return sequenceFactory = new SequenceFactoryImpl(workerId,centerId);
        }
        return sequenceFactory;
    }

    /**
     * 私 有 构 造 方 法
     * */
    private SequenceFactoryImpl(long workerId, long centerId) {

        if (workerId > maxWorkerId || workerId < 0) {
            throw new SequenceException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
        }
        if (centerId > maxCenterId || centerId < 0) {
            throw new SequenceException(String.format("center Id can't be greater than %d or less than 0", maxCenterId));
        }
        this.workerId = workerId;
        this.centerId = centerId;
    }

    /**
     * 获 取 下 一 个 ID
     * */
    public synchronized Sequence makeSequence() {
        long timestamp = timeGen();
        if (timestamp < lastTimestamp) {
            throw new SequenceException(String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
        }
        if (lastTimestamp == timestamp) {
            sequence = (sequence + 1) & sequenceMask;
            if (sequence == 0) {
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            sequence = 0L;
        }
        lastTimestamp = timestamp;

        long currentId = ((timestamp - poc) << timestampLeftShift) | (centerId << centerIdShift) | (workerId << workerIdShift) | sequence;

        Sequence sequence = new Sequence();
        sequence.setCurrentId(currentId);
        sequence.setCurrentTime(timestamp);
        sequence.setCenterId(centerId);
        sequence.setWorkerId(workerId);

        return sequence;
    }

    @Override
    public Set<Sequence> makeSequence(int batchSize) {

        Set<Sequence> sequences = new HashSet<>();

        for (long current = 0; current < batchSize; current++){
            sequences.add(makeSequence());
        }

        return sequences;
    }

    /**
     * 获 取 下 一 个 Id
     * */
    public long makeId(){
        return makeSequence().getCurrentId();
    }

    /**
     * 根 据 一 定 数 量 的 Id
     * */
    public Set<Long> makeId(int initSize){
        Set<Long> ids = new HashSet<>(initSize);
        for (long current = 0; current < initSize; current++){
            ids.add(makeId());
        }
        return ids;
    }

    /**
     * 时 间 戳 比 对
     * */
    protected long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }

    /**
     * 当 前 系 统 时 间 毫 秒
     * */
    protected long timeGen() {

        return System.currentTimeMillis();

    }

}
